package org.spiderflow.common.io;

import org.apache.logging.log4j.util.Strings;

import java.io.Closeable;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * 随机存取文件阅读器
 *
 * @author zsh
 */
public class RandomAccessFileReader implements Closeable {

    private final RandomAccessFile raf;

    /**
     * 从index位置开始读取
     */
    private long index;

    /**
     * 读取顺序，默认倒叙
     */
    private final boolean reversed;

    /**
     * 缓冲区大小
     */
    private final int bufSize;

    public RandomAccessFileReader(RandomAccessFile raf, long index, boolean reversed) throws IOException {
        this(raf, index, 1024, reversed);
    }

    public RandomAccessFileReader(RandomAccessFile raf, long index, int bufSize, boolean reversed) throws IOException {
        if (raf == null) {
            throw new NullPointerException("file is null");
        }
        this.raf = raf;
        this.reversed = reversed;
        this.bufSize = bufSize;
        this.index = index;
        this.init();
    }

    private void init() throws IOException {
        if (reversed) {
            this.index = this.index == -1 ? this.raf.length() : Math.min(this.index, this.raf.length());
        } else {
            this.index = Math.min(Math.max(this.index, 0), this.raf.length());
        }
        if (this.index > 0) {
            this.raf.seek(this.index);
        }
    }

    /**
     * 读取n行
     *
     * @param n         要读取的行数
     * @param keywords  搜索的关键词
     * @param matchCase 是否区分大小写
     * @param regx      是否是正则搜索
     * @return 返回Line对象，包含行的起始位置与终止位置
     */
    public List<Line> readLine(int n, String keywords, boolean matchCase, boolean regx) throws IOException {
        List<Line> lines = new ArrayList<>(n);
        long lastNewlineIndex = getLastNewlineIndex();
        boolean keywordEmpty = Strings.isEmpty(keywords);
        Pattern pattern = regx && !keywordEmpty ? Pattern.compile(keywords) : null;
        while (n - lines.size() > 0) {
            byte[] buf = buildBuffer();
            int len = this.raf.read(buf, 0, buf.length);
            if (len <= 0) {
                //已读完
                break;
            }
            TextFindHandler handler = new TextFindHandler(keywords, matchCase, pattern);
            lastNewlineIndex = readLineAndFind(n, buf, len, lastNewlineIndex, handler, lines);
            if (!reversed) {
                this.index = this.index + buf.length;
            }
        }
        boolean lineLess = n - lines.size() > 0;
        boolean notEnd = lastNewlineIndex > 1;
        boolean isFind = keywordEmpty || !lines.isEmpty();
        if (reversed && lineLess && notEnd && isFind) {
            lines.add(0, readLine(0, lastNewlineIndex));
        }
        return lines;
    }

    private long readLineAndFind(int n, byte[] buf, int len, long lastNewlineIndex,
                                 TextFindHandler handler, List<Line> lines) throws IOException {
        long newlineIndex = lastNewlineIndex;
        boolean isFind = Strings.isEmpty(handler.getKeywords());
        for (int i = 0; i < len && n - lines.size() > 0; i++) {
            int readIndex = reversed ? len - i - 1 : i;
            if (isNotNewline(newlineIndex, buf, readIndex)) {
                continue;
            }
            //计算起止位置
            long fromIndex = reversed ? this.index + readIndex : newlineIndex;
            //计算终止位置
            long endIndex = reversed ? newlineIndex : this.index + readIndex;
            //取出文本
            Line line = readLine(fromIndex + 1, endIndex);
            isFind = findAndAddLine(handler, lines, isFind, line);
            //记录上次读取到的\r或\n位置
            newlineIndex = this.index + readIndex;
        }
        return newlineIndex;
    }

    private boolean findAndAddLine(TextFindHandler textFindHandler,
                                   List<Line> lines, boolean isFind, Line line) {
        if (isFind) {
            addLine(lines, line);
            return true;
        } else {
            //定位查找，使被查找的行始终在第一行
            boolean find = textFindHandler.find(line.getText());
            if (find) {
                addLine(lines, line);
            }
            return find;
        }
    }

    private void addLine(List<Line> lines, Line line) {
        if (reversed) {
            //反向查找时，插入到List头部
            lines.add(0, line);
        } else {
            lines.add(line);
        }
    }

    private boolean isNotNewline(long lastNewlineIndex, byte[] buf, int readIndex) {
        //如果没有读取到\r或\n
        if (!isNewline(buf[readIndex])) {
            return true;
        }
        //两行之间的间距,当=1时则代表有\r\n,\n\r,\r\r,\n\n四种情况之一
        long spacing = this.index + readIndex - lastNewlineIndex;
        return Math.abs(spacing) <= 1;
    }

    private byte[] buildBuffer() throws IOException {
        byte[] buf = reversed ? new byte[(int) Math.min(this.bufSize, this.index)] : new byte[this.bufSize];
        if (this.reversed) {
            if (this.index == 0) {
                return new byte[0];
            }
            this.index = this.index - buf.length;
            this.raf.seek(this.index);
        }
        return buf;
    }

    private long getLastNewlineIndex() {
        if (reversed) {
            return this.index;
        }
        return this.index > 0 ? this.index + 1 : -1;
    }

    /**
     * 从指定位置读取一行
     *
     * @param fromIndex 开始位置
     * @param endIndex  结束位置
     * @return 返回Line对象
     * @throws IOException
     */
    private Line readLine(long fromIndex, long endIndex) throws IOException {
        long filePointer = this.raf.getFilePointer();
        this.raf.seek(fromIndex);
        byte[] buf = new byte[(int) (endIndex - fromIndex)];
        this.raf.read(buf, 0, buf.length);
        Line line = new Line(fromIndex, new String(buf), endIndex);
        this.raf.seek(filePointer);
        return line;
    }

    private boolean isNewline(byte b) {
        return b == 13 || b == 10;
    }

    @Override
    public void close() throws IOException {
        if (this.raf != null) {
            this.raf.close();
        }
    }
}
